@@ -59,6 +59,7 @@ gem 'faraday', '~> 0.9.0' |
||
| 59 | 59 |
gem 'faraday_middleware' |
| 60 | 60 |
gem 'typhoeus', '~> 0.6.3' |
| 61 | 61 |
gem 'nokogiri', '~> 1.6.1' |
| 62 |
+gem 'net-ftp-list', '~> 3.2.8' |
|
| 62 | 63 |
|
| 63 | 64 |
gem 'wunderground', '~> 1.2.0' |
| 64 | 65 |
gem 'forecast_io', '~> 2.0.0' |
@@ -188,6 +188,7 @@ GEM |
||
| 188 | 188 |
multipart-post (2.0.0) |
| 189 | 189 |
mysql2 (0.3.16) |
| 190 | 190 |
naught (1.0.0) |
| 191 |
+ net-ftp-list (3.2.8) |
|
| 191 | 192 |
nokogiri (1.6.3.1) |
| 192 | 193 |
mini_portile (= 0.6.0) |
| 193 | 194 |
oauth (0.4.7) |
@@ -418,6 +419,7 @@ DEPENDENCIES |
||
| 418 | 419 |
liquid (~> 2.6.1) |
| 419 | 420 |
mqtt |
| 420 | 421 |
mysql2 (~> 0.3.16) |
| 422 |
+ net-ftp-list (~> 3.2.8) |
|
| 421 | 423 |
nokogiri (~> 1.6.1) |
| 422 | 424 |
omniauth |
| 423 | 425 |
omniauth-37signals |
@@ -27,6 +27,8 @@ Follow [@tectonic](https://twitter.com/tectonic) for updates as Huginn evolves, |
||
| 27 | 27 |
|
| 28 | 28 |
Want to help with Huginn? All contributions are encouraged! You could make UI improvements, add new Agents, write documentation and tutorials, or try tackling [issues tagged with #help-wanted](https://github.com/cantino/huginn/issues?direction=desc&labels=help-wanted&page=1&sort=created&state=open). |
| 29 | 29 |
|
| 30 |
+Really want an issue fixed/feature implemented? Or maybe you just want to solve some community issues and earn some extra coffee money? Then you should take a look at the [current bounties on Bountysource](https://www.bountysource.com/trackers/282580-huginn). |
|
| 31 |
+ |
|
| 30 | 32 |
Have an awesome an idea but not feeling quite up to contributing yet? Head over to our [Official 'suggest an agent' thread ](https://github.com/cantino/huginn/issues/353) and tell us about your cool idea! |
| 31 | 33 |
|
| 32 | 34 |
## Examples |
@@ -105,5 +107,5 @@ Huginn is a work in progress and is just getting started. Please get involved! |
||
| 105 | 107 |
|
| 106 | 108 |
Please fork, add specs, and send pull requests! |
| 107 | 109 |
|
| 108 |
-[](https://travis-ci.org/cantino/huginn) [](https://coveralls.io/r/cantino/huginn) [](https://bitdeli.com/free "Bitdeli Badge") [](https://gemnasium.com/cantino/huginn) |
|
| 110 |
+[](https://travis-ci.org/cantino/huginn) [](https://coveralls.io/r/cantino/huginn) [](https://bitdeli.com/free "Bitdeli Badge") [](https://gemnasium.com/cantino/huginn) [](https://www.bountysource.com/trackers/282580-huginn?utm_source=282580&utm_medium=shield&utm_campaign=TRACKER_BADGE) |
|
| 109 | 111 |
|
@@ -0,0 +1,20 @@ |
||
| 1 |
+$ -> |
|
| 2 |
+ svg = document.querySelector('.agent-diagram svg.diagram')
|
|
| 3 |
+ overlay = document.querySelector('.agent-diagram .overlay')
|
|
| 4 |
+ getTopLeft = (node) -> |
|
| 5 |
+ bbox = node.getBBox() |
|
| 6 |
+ point = svg.createSVGPoint() |
|
| 7 |
+ point.x = bbox.x + bbox.width |
|
| 8 |
+ point.y = bbox.y |
|
| 9 |
+ point.matrixTransform(node.getCTM()) |
|
| 10 |
+ $(svg).find('g.node[data-badge-id]').each ->
|
|
| 11 |
+ tl = getTopLeft(this) |
|
| 12 |
+ $('#' + this.getAttribute('data-badge-id'), overlay).each ->
|
|
| 13 |
+ badge = $(this) |
|
| 14 |
+ badge.css |
|
| 15 |
+ left: tl.x - badge.outerWidth() * (2/3) |
|
| 16 |
+ top: tl.y - badge.outerHeight() * (1/3) |
|
| 17 |
+ 'background-color': badge.find('.label').css('background-color')
|
|
| 18 |
+ .show() |
|
| 19 |
+ return |
|
| 20 |
+ return |
@@ -0,0 +1,30 @@ |
||
| 1 |
+.agent-diagram {
|
|
| 2 |
+ position: relative; |
|
| 3 |
+ z-index: auto; |
|
| 4 |
+ |
|
| 5 |
+ svg.diagram {
|
|
| 6 |
+ position: absolute; |
|
| 7 |
+ z-index: 1; |
|
| 8 |
+ } |
|
| 9 |
+ |
|
| 10 |
+ .overlay-container {
|
|
| 11 |
+ position: absolute; |
|
| 12 |
+ top: 0; |
|
| 13 |
+ left: 0; |
|
| 14 |
+ z-index: auto; |
|
| 15 |
+ |
|
| 16 |
+ .overlay {
|
|
| 17 |
+ position: relative; |
|
| 18 |
+ z-index: auto; |
|
| 19 |
+ width: 100%; |
|
| 20 |
+ height: 100%; |
|
| 21 |
+ |
|
| 22 |
+ .badge {
|
|
| 23 |
+ position: absolute; |
|
| 24 |
+ display: none; |
|
| 25 |
+ color: white !important; |
|
| 26 |
+ z-index: 2; |
|
| 27 |
+ } |
|
| 28 |
+ } |
|
| 29 |
+ } |
|
| 30 |
+} |
@@ -98,14 +98,6 @@ class AgentsController < ApplicationController |
||
| 98 | 98 |
@agent = current_user.agents.find(params[:id]) |
| 99 | 99 |
end |
| 100 | 100 |
|
| 101 |
- def diagram |
|
| 102 |
- @agents = if params[:scenario_id].present? |
|
| 103 |
- current_user.scenarios.find(params[:scenario_id]).agents.includes(:receivers) |
|
| 104 |
- else |
|
| 105 |
- current_user.agents.includes(:receivers) |
|
| 106 |
- end |
|
| 107 |
- end |
|
| 108 |
- |
|
| 109 | 101 |
def create |
| 110 | 102 |
@agent = Agent.build_for_type(params[:agent].delete(:type), |
| 111 | 103 |
current_user, |
@@ -0,0 +1,9 @@ |
||
| 1 |
+class DiagramsController < ApplicationController |
|
| 2 |
+ def show |
|
| 3 |
+ @agents = if params[:scenario_id].present? |
|
| 4 |
+ current_user.scenarios.find(params[:scenario_id]).agents.includes(:receivers) |
|
| 5 |
+ else |
|
| 6 |
+ current_user.agents.includes(:receivers) |
|
| 7 |
+ end |
|
| 8 |
+ end |
|
| 9 |
+end |
@@ -2,8 +2,8 @@ class EventsController < ApplicationController |
||
| 2 | 2 |
before_filter :load_event, :except => :index |
| 3 | 3 |
|
| 4 | 4 |
def index |
| 5 |
- if params[:agent] |
|
| 6 |
- @agent = current_user.agents.find(params[:agent]) |
|
| 5 |
+ if params[:agent_id] |
|
| 6 |
+ @agent = current_user.agents.find(params[:agent_id]) |
|
| 7 | 7 |
@events = @agent.events.page(params[:page]) |
| 8 | 8 |
else |
| 9 | 9 |
@events = current_user.events.preload(:agent).page(params[:page]) |
@@ -6,7 +6,7 @@ module DotHelper |
||
| 6 | 6 |
dot.close_write |
| 7 | 7 |
dot.read |
| 8 | 8 |
} rescue false) |
| 9 |
- svg.html_safe |
|
| 9 |
+ decorate_svg(svg, agents).html_safe |
|
| 10 | 10 |
else |
| 11 | 11 |
tag('img', src: URI('https://chart.googleapis.com/chart').tap { |uri|
|
| 12 | 12 |
uri.query = URI.encode_www_form(cht: 'gv', chl: agents_dot(agents)) |
@@ -57,6 +57,13 @@ module DotHelper |
||
| 57 | 57 |
end |
| 58 | 58 |
end |
| 59 | 59 |
|
| 60 |
+ def ids(values) |
|
| 61 |
+ values.each_with_index { |id, i|
|
|
| 62 |
+ raw ' ' if i > 0 |
|
| 63 |
+ id id |
|
| 64 |
+ } |
|
| 65 |
+ end |
|
| 66 |
+ |
|
| 60 | 67 |
def attr_list(attrs = nil) |
| 61 | 68 |
return if attrs.nil? |
| 62 | 69 |
attrs = attrs.select { |key, value| value.present? }
|
@@ -86,16 +93,13 @@ module DotHelper |
||
| 86 | 93 |
end |
| 87 | 94 |
|
| 88 | 95 |
def statement(ids, attrs = nil) |
| 89 |
- Array(ids).each_with_index { |id, i|
|
|
| 90 |
- raw ' ' if i > 0 |
|
| 91 |
- id id |
|
| 92 |
- } |
|
| 96 |
+ ids Array(ids) |
|
| 93 | 97 |
attr_list attrs |
| 94 | 98 |
raw ';' |
| 95 | 99 |
end |
| 96 | 100 |
|
| 97 |
- def block(title, &block) |
|
| 98 |
- raw title |
|
| 101 |
+ def block(*ids, &block) |
|
| 102 |
+ ids ids |
|
| 99 | 103 |
raw '{'
|
| 100 | 104 |
block.call |
| 101 | 105 |
raw '}' |
@@ -112,11 +116,7 @@ module DotHelper |
||
| 112 | 116 |
draw(agents: agents, |
| 113 | 117 |
agent_id: ->agent { 'a%d' % agent.id },
|
| 114 | 118 |
agent_label: ->agent {
|
| 115 |
- if agent.disabled? |
|
| 116 |
- '%s (Disabled)' % agent.name |
|
| 117 |
- else |
|
| 118 |
- agent.name |
|
| 119 |
- end.gsub(/(.{20}\S*)\s+/) {
|
|
| 119 |
+ agent.name.gsub(/(.{20}\S*)\s+/) {
|
|
| 120 | 120 |
# Fold after every 20+ characters |
| 121 | 121 |
$1 + "\n" |
| 122 | 122 |
} |
@@ -128,6 +128,7 @@ module DotHelper |
||
| 128 | 128 |
def agent_node(agent) |
| 129 | 129 |
node(agent_id[agent], |
| 130 | 130 |
label: agent_label[agent], |
| 131 |
+ tooltip: (agent.short_type.titleize if rich), |
|
| 131 | 132 |
URL: (agent_url[agent] if rich), |
| 132 | 133 |
style: ('rounded,dashed' if agent.disabled?),
|
| 133 | 134 |
color: (@disabled if agent.disabled?), |
@@ -141,7 +142,7 @@ module DotHelper |
||
| 141 | 142 |
color: (@disabled if agent.disabled? || receiver.disabled?)) |
| 142 | 143 |
end |
| 143 | 144 |
|
| 144 |
- block('digraph foo') {
|
|
| 145 |
+ block('digraph', 'Agent Event Flow') {
|
|
| 145 | 146 |
# statement 'graph', rankdir: 'LR' |
| 146 | 147 |
statement 'node', |
| 147 | 148 |
shape: 'box', |
@@ -160,4 +161,60 @@ module DotHelper |
||
| 160 | 161 |
} |
| 161 | 162 |
} |
| 162 | 163 |
end |
| 164 |
+ |
|
| 165 |
+ def decorate_svg(xml, agents) |
|
| 166 |
+ svg = Nokogiri::XML(xml).at('svg')
|
|
| 167 |
+ |
|
| 168 |
+ Nokogiri::HTML::Document.new.tap { |doc|
|
|
| 169 |
+ doc << root = Nokogiri::XML::Node.new('div', doc) { |div|
|
|
| 170 |
+ div['class'] = 'agent-diagram' |
|
| 171 |
+ } |
|
| 172 |
+ |
|
| 173 |
+ svg['class'] = 'diagram' |
|
| 174 |
+ |
|
| 175 |
+ root << svg |
|
| 176 |
+ root << overlay_container = Nokogiri::XML::Node.new('div', doc) { |div|
|
|
| 177 |
+ div['class'] = 'overlay-container' |
|
| 178 |
+ div['style'] = "width: #{svg['width']}; height: #{svg['height']}"
|
|
| 179 |
+ } |
|
| 180 |
+ overlay_container << overlay = Nokogiri::XML::Node.new('div', doc) { |div|
|
|
| 181 |
+ div['class'] = 'overlay' |
|
| 182 |
+ } |
|
| 183 |
+ |
|
| 184 |
+ svg.xpath('//xmlns:g[@class="node"]', svg.namespaces).each { |node|
|
|
| 185 |
+ agent_id = (node.xpath('./xmlns:title/text()', svg.namespaces).to_s[/\d+/] or next).to_i
|
|
| 186 |
+ agent = agents.find { |a| a.id == agent_id }
|
|
| 187 |
+ |
|
| 188 |
+ count = agent.events_count |
|
| 189 |
+ next unless count && count > 0 |
|
| 190 |
+ |
|
| 191 |
+ overlay << Nokogiri::XML::Node.new('a', doc) { |badge|
|
|
| 192 |
+ badge['id'] = id = 'b%d' % agent_id |
|
| 193 |
+ badge['class'] = 'badge' |
|
| 194 |
+ badge['href'] = events_path(agent: agent) |
|
| 195 |
+ badge['target'] = '_blank' |
|
| 196 |
+ badge['title'] = "#{count} events created"
|
|
| 197 |
+ badge.content = count.to_s |
|
| 198 |
+ |
|
| 199 |
+ node['data-badge-id'] = id |
|
| 200 |
+ |
|
| 201 |
+ badge << Nokogiri::XML::Node.new('span', doc) { |label|
|
|
| 202 |
+ # a dummy label only to obtain the background color |
|
| 203 |
+ label['class'] = [ |
|
| 204 |
+ 'label', |
|
| 205 |
+ if agent.disabled? |
|
| 206 |
+ 'label-warning' |
|
| 207 |
+ elsif agent.working? |
|
| 208 |
+ 'label-success' |
|
| 209 |
+ else |
|
| 210 |
+ 'label-danger' |
|
| 211 |
+ end |
|
| 212 |
+ ].join(' ')
|
|
| 213 |
+ label['style'] = 'display: none'; |
|
| 214 |
+ } |
|
| 215 |
+ } |
|
| 216 |
+ } |
|
| 217 |
+ # See also: app/assets/diagram.js.coffee |
|
| 218 |
+ }.at('div.agent-diagram').to_s
|
|
| 219 |
+ end |
|
| 163 | 220 |
end |
@@ -25,9 +25,14 @@ module Agents |
||
| 25 | 25 |
|
| 26 | 26 |
"instructions": {
|
| 27 | 27 |
"message": "Today's conditions look like {{conditions}} with a high temperature of {{high.celsius}} degrees Celsius.",
|
| 28 |
- "subject": "{{data}}"
|
|
| 28 |
+ "subject": "{{data}}",
|
|
| 29 |
+ "created_at": "{{created_at}}"
|
|
| 29 | 30 |
} |
| 30 | 31 |
|
| 32 |
+ Names here like `conditions`, `high` and `data` refer to the corresponding values in the Event hash. |
|
| 33 |
+ |
|
| 34 |
+ The special key `created_at` refers to the timestamp of the Event, which can be reformatted by the `date` filter, like `{{created_at | date:"at %I:%M %p" }}`.
|
|
| 35 |
+ |
|
| 31 | 36 |
The upstream agent of each received event is accessible via the key `agent`, which has the following attributes: #{''.tap { |s| s << AgentDrop.instance_methods(false).map { |m| "`#{m}`" }.join(', ') }}.
|
| 32 | 37 |
|
| 33 | 38 |
Have a look at the [Wiki](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid) to learn more about liquid templating. |
@@ -68,8 +73,6 @@ module Agents |
||
| 68 | 73 |
|
| 69 | 74 |
If you want to retain original contents of events and only add new keys, then set `mode` to `merge`, otherwise set it to `clean`. |
| 70 | 75 |
|
| 71 |
- By default, the output event will have a `created_at` field added as well, reflecting the original Event creation time. You can skip this output by setting `skip_created_at` to `true`. |
|
| 72 |
- |
|
| 73 | 76 |
To CGI escape output (for example when creating a link), use the Liquid `uri_escape` filter, like so: |
| 74 | 77 |
|
| 75 | 78 |
{
|
@@ -82,7 +85,7 @@ module Agents |
||
| 82 | 85 |
after_save :clear_matchers |
| 83 | 86 |
|
| 84 | 87 |
def validate_options |
| 85 |
- errors.add(:base, "instructions, mode, and skip_created_at all need to be present.") unless options['instructions'].present? && options['mode'].present? && options['skip_created_at'].present? |
|
| 88 |
+ errors.add(:base, "instructions and mode need to be present.") unless options['instructions'].present? && options['mode'].present? |
|
| 86 | 89 |
|
| 87 | 90 |
validate_matchers |
| 88 | 91 |
end |
@@ -96,7 +99,6 @@ module Agents |
||
| 96 | 99 |
}, |
| 97 | 100 |
'matchers' => [], |
| 98 | 101 |
'mode' => "clean", |
| 99 |
- 'skip_created_at' => "false" |
|
| 100 | 102 |
} |
| 101 | 103 |
end |
| 102 | 104 |
|
@@ -110,7 +112,6 @@ module Agents |
||
| 110 | 112 |
opts = interpolated(event.to_liquid(payload)) |
| 111 | 113 |
formatted_event = opts['mode'].to_s == "merge" ? event.payload.dup : {}
|
| 112 | 114 |
formatted_event.merge! opts['instructions'] |
| 113 |
- formatted_event['created_at'] = event.created_at unless opts['skip_created_at'].to_s == "true" |
|
| 114 | 115 |
create_event :payload => formatted_event |
| 115 | 116 |
end |
| 116 | 117 |
end |
@@ -1,4 +1,5 @@ |
||
| 1 | 1 |
require 'net/ftp' |
| 2 |
+require 'net/ftp/list' |
|
| 2 | 3 |
require 'uri' |
| 3 | 4 |
require 'time' |
| 4 | 5 |
|
@@ -105,34 +106,15 @@ module Agents |
||
| 105 | 106 |
# commands during iteration. |
| 106 | 107 |
list = ftp.list('-a')
|
| 107 | 108 |
|
| 108 |
- month2year = {}
|
|
| 109 |
- |
|
| 110 | 109 |
list.each do |line| |
| 111 |
- mon, day, smtn, rest = line.split(' ', 9)[5..-1]
|
|
| 112 |
- |
|
| 113 |
- # Remove symlink target part if any |
|
| 114 |
- filename = rest[/\A(.+?)(?:\s+->\s|\z)/, 1] |
|
| 115 |
- |
|
| 110 |
+ entry = Net::FTP::List.parse line |
|
| 111 |
+ filename = entry.basename |
|
| 112 |
+ mtime = Time.parse(entry.mtime.to_s).utc |
|
| 113 |
+ |
|
| 116 | 114 |
patterns.any? { |pattern|
|
| 117 | 115 |
File.fnmatch?(pattern, filename) |
| 118 | 116 |
} or next |
| 119 | 117 |
|
| 120 |
- case smtn |
|
| 121 |
- when /:/ |
|
| 122 |
- if year = month2year[mon] |
|
| 123 |
- mtime = Time.parse("#{mon} #{day} #{year} #{smtn} GMT")
|
|
| 124 |
- else |
|
| 125 |
- log "Getting mtime of #{filename}"
|
|
| 126 |
- mtime = ftp.mtime(filename) |
|
| 127 |
- month2year[mon] = mtime.year |
|
| 128 |
- end |
|
| 129 |
- else |
|
| 130 |
- # Do not bother calling MDTM for old files. Losing the |
|
| 131 |
- # time part only makes a timestamp go backwards, meaning |
|
| 132 |
- # that it will trigger no new event. |
|
| 133 |
- mtime = Time.parse("#{mon} #{day} #{smtn} GMT")
|
|
| 134 |
- end |
|
| 135 |
- |
|
| 136 | 118 |
after < mtime or next |
| 137 | 119 |
|
| 138 | 120 |
yield filename, mtime |
@@ -193,7 +175,7 @@ module Agents |
||
| 193 | 175 |
found_entries[filename] |
| 194 | 176 |
}.each { |filename|
|
| 195 | 177 |
create_event :payload => {
|
| 196 |
- 'url' => (base_uri + filename).to_s, |
|
| 178 |
+ 'url' => "#{base_uri}#{filename}",
|
|
| 197 | 179 |
'filename' => filename, |
| 198 | 180 |
'timestamp' => found_entries[filename], |
| 199 | 181 |
} |
@@ -31,7 +31,7 @@ module Agents |
||
| 31 | 31 |
end |
| 32 | 32 |
|
| 33 | 33 |
def validate_options |
| 34 |
- errors.add(:base, "you need to specify a hipchat auth_token") unless options['auth_token'].present? |
|
| 34 |
+ errors.add(:base, "you need to specify a hipchat auth_token or provide a credential named hipchat_auth_token") unless options['auth_token'].present? || credential('hipchat_auth_token').present?
|
|
| 35 | 35 |
errors.add(:base, "you need to specify a room_name or a room_name_path") if options['room_name'].blank? && options['room_name_path'].blank? |
| 36 | 36 |
end |
| 37 | 37 |
|
@@ -40,10 +40,10 @@ module Agents |
||
| 40 | 40 |
end |
| 41 | 41 |
|
| 42 | 42 |
def receive(incoming_events) |
| 43 |
- client = HipChat::Client.new(interpolated[:auth_token]) |
|
| 43 |
+ client = HipChat::Client.new(interpolated[:auth_token] || credential('hipchat_auth_token'))
|
|
| 44 | 44 |
incoming_events.each do |event| |
| 45 | 45 |
mo = interpolated(event) |
| 46 |
- client[mo[:room_name]].send(mo[:username], mo[:message], :notify => mo[:notify].to_s == 'true' ? 1 : 0, :color => mo[:color]) |
|
| 46 |
+ client[mo[:room_name]].send(mo[:username], mo[:message], :notify => boolify(mo[:notify]) ? 1 : 0, :color => mo[:color]) |
|
| 47 | 47 |
end |
| 48 | 48 |
end |
| 49 | 49 |
end |
@@ -17,7 +17,7 @@ module Agents |
||
| 17 | 17 |
|
| 18 | 18 |
Simply choose a topic (think email subject line) to publish/listen to, and configure your service. |
| 19 | 19 |
|
| 20 |
- It's easy to setup your own [broker](http://jpmens.net/2013/09/01/installing-mosquitto-on-a-raspberry-pi/) or connect to a [cloud service](www.cloudmqtt.com) |
|
| 20 |
+ It's easy to setup your own [broker](http://jpmens.net/2013/09/01/installing-mosquitto-on-a-raspberry-pi/) or connect to a [cloud service](http://www.cloudmqtt.com) |
|
| 21 | 21 |
|
| 22 | 22 |
Hints: |
| 23 | 23 |
Many services run mqtts (mqtt over SSL) often with a custom certificate. |
@@ -69,7 +69,7 @@ module Agents |
||
| 69 | 69 |
def receive(incoming_events) |
| 70 | 70 |
incoming_events.each do |event| |
| 71 | 71 |
outgoing = interpolated(event)['payload'].presence || {}
|
| 72 |
- if interpolated['no_merge'].to_s == 'true' |
|
| 72 |
+ if boolify(interpolated['no_merge']) |
|
| 73 | 73 |
handle outgoing, event.payload |
| 74 | 74 |
else |
| 75 | 75 |
handle outgoing.merge(event.payload), event.payload |
@@ -102,7 +102,7 @@ module Agents |
||
| 102 | 102 |
end |
| 103 | 103 |
|
| 104 | 104 |
def keep_event? |
| 105 |
- interpolated['keep_event'] == 'true' |
|
| 105 |
+ boolify(interpolated['keep_event']) |
|
| 106 | 106 |
end |
| 107 | 107 |
end |
| 108 | 108 |
end |
@@ -44,13 +44,13 @@ module Agents |
||
| 44 | 44 |
incoming_events.each do |event| |
| 45 | 45 |
message = (event.payload['message'].presence || event.payload['text'].presence || event.payload['sms'].presence).to_s |
| 46 | 46 |
if message.present? |
| 47 |
- if interpolated(event)['receive_call'].to_s == 'true' |
|
| 47 |
+ if boolify(interpolated(event)['receive_call']) |
|
| 48 | 48 |
secret = SecureRandom.hex 3 |
| 49 | 49 |
memory['pending_calls'][secret] = message |
| 50 | 50 |
make_call secret |
| 51 | 51 |
end |
| 52 | 52 |
|
| 53 |
- if interpolated(event)['receive_text'].to_s == 'true' |
|
| 53 |
+ if boolify(interpolated(event)['receive_text']) |
|
| 54 | 54 |
message = message.slice 0..160 |
| 55 | 55 |
send_message message |
| 56 | 56 |
end |
@@ -56,6 +56,8 @@ class EventDrop |
||
| 56 | 56 |
case key |
| 57 | 57 |
when 'agent' |
| 58 | 58 |
@object.agent |
| 59 |
+ when 'created_at' |
|
| 60 |
+ @object.created_at |
|
| 59 | 61 |
end |
| 60 | 62 |
end |
| 61 | 63 |
end |
@@ -53,7 +53,7 @@ |
||
| 53 | 53 |
</td> |
| 54 | 54 |
<td class='<%= "agent-disabled" if agent.disabled? %>'> |
| 55 | 55 |
<% if agent.can_create_events? %> |
| 56 |
- <%= link_to(agent.events_count || 0, events_path(:agent => agent.to_param)) %> |
|
| 56 |
+ <%= link_to(agent.events_count || 0, agent_events_path(agent)) %> |
|
| 57 | 57 |
<% else %> |
| 58 | 58 |
<span class='not-applicable'></span> |
| 59 | 59 |
<% end %> |
@@ -12,9 +12,8 @@ |
||
| 12 | 12 |
<div class="btn-group"> |
| 13 | 13 |
<%= link_to '<span class="glyphicon glyphicon-plus"></span> New Agent'.html_safe, new_agent_path, class: "btn btn-default" %> |
| 14 | 14 |
<%= link_to '<span class="glyphicon glyphicon-refresh"></span> Run event propagation'.html_safe, propagate_agents_path, method: 'post', class: "btn btn-default" %> |
| 15 |
- <%= link_to '<span class="glyphicon glyphicon-random"></span> View diagram'.html_safe, diagram_agents_path, class: "btn btn-default" %> |
|
| 15 |
+ <%= link_to '<span class="glyphicon glyphicon-random"></span> View diagram'.html_safe, diagram_path, class: "btn btn-default" %> |
|
| 16 | 16 |
</div> |
| 17 | 17 |
</div> |
| 18 | 18 |
</div> |
| 19 | 19 |
</div> |
| 20 |
- |
@@ -15,7 +15,7 @@ |
||
| 15 | 15 |
<li><a href="#logs" data-toggle="tab" data-agent-id="<%= @agent.id %>" class='<%= @agent.recent_error_logs? ? 'recent-errors' : '' %>'><span class='glyphicon glyphicon-list-alt'></span> Logs</a></li> |
| 16 | 16 |
|
| 17 | 17 |
<% if @agent.can_create_events? && @agent.events.count > 0 %> |
| 18 |
- <li><%= link_to '<span class="glyphicon glyphicon-random"></span> Events'.html_safe, events_path(:agent => @agent.to_param) %></li> |
|
| 18 |
+ <li><%= link_to '<span class="glyphicon glyphicon-random"></span> Events'.html_safe, agent_events_path(@agent) %></li> |
|
| 19 | 19 |
<% else %> |
| 20 | 20 |
<li class='disabled'><a><span class='glyphicon glyphicon-random'></span> Events</a></li> |
| 21 | 21 |
<% end %> |
@@ -103,7 +103,7 @@ |
||
| 103 | 103 |
<% if @agent.can_create_events? %> |
| 104 | 104 |
<p> |
| 105 | 105 |
<b>Events created:</b> |
| 106 |
- <%= link_to @agent.events.count, events_path(:agent => @agent.to_param) %> |
|
| 106 |
+ <%= link_to @agent.events.count, agent_events_path(@agent) %> |
|
| 107 | 107 |
</p> |
| 108 | 108 |
<% end %> |
| 109 | 109 |
|
@@ -1,3 +1,7 @@ |
||
| 1 |
+<% content_for :head do %> |
|
| 2 |
+ <%= javascript_include_tag "diagram" %> |
|
| 3 |
+<% end %> |
|
| 4 |
+ |
|
| 1 | 5 |
<div class='container'> |
| 2 | 6 |
<div class='row'> |
| 3 | 7 |
<div class='col-md-12'> |
@@ -41,7 +41,7 @@ |
||
| 41 | 41 |
agentPaths["New Agent"] = <%= Utils.jsonify new_agent_path %>; |
| 42 | 42 |
agentPaths["Account"] = <%= Utils.jsonify edit_user_registration_path %>; |
| 43 | 43 |
agentPaths["Events Index"] = <%= Utils.jsonify events_path %>; |
| 44 |
- agentPaths["View Agent Diagram"] = <%= Utils.jsonify diagram_agents_path %>; |
|
| 44 |
+ agentPaths["View Agent Diagram"] = <%= Utils.jsonify diagram_path %>; |
|
| 45 | 45 |
agentPaths["Run Event Propagation"] = { url: <%= Utils.jsonify propagate_agents_path %>, method: 'POST' };
|
| 46 | 46 |
|
| 47 | 47 |
|
@@ -15,7 +15,7 @@ |
||
| 15 | 15 |
|
| 16 | 16 |
<div class="btn-group"> |
| 17 | 17 |
<%= link_to '<span class="glyphicon glyphicon-chevron-left"></span> Back'.html_safe, scenarios_path, class: "btn btn-default" %> |
| 18 |
- <%= link_to '<span class="glyphicon glyphicon-random"></span> View Diagram'.html_safe, diagram_agents_path(:scenario_id => @scenario.to_param), class: "btn btn-default" %> |
|
| 18 |
+ <%= link_to '<span class="glyphicon glyphicon-random"></span> View Diagram'.html_safe, scenario_diagram_path(@scenario), class: "btn btn-default" %> |
|
| 19 | 19 |
<%= link_to '<span class="glyphicon glyphicon-edit"></span> Edit'.html_safe, edit_scenario_path(@scenario), class: "btn btn-default" %> |
| 20 | 20 |
<% if @scenario.source_url.present? %> |
| 21 | 21 |
<%= link_to '<span class="glyphicon glyphicon-plus"></span> Update'.html_safe, new_scenario_imports_path(:url => @scenario.source_url), class: "btn btn-default" %> |
@@ -61,7 +61,7 @@ Huginn::Application.configure do |
||
| 61 | 61 |
end |
| 62 | 62 |
|
| 63 | 63 |
# Precompile additional assets (application.js.coffee.erb, application.css, and all non-JS/CSS are already added) |
| 64 |
- config.assets.precompile += %w( graphing.js user_credentials.js ) |
|
| 64 |
+ config.assets.precompile += %w( diagram.js graphing.js user_credentials.js ) |
|
| 65 | 65 |
|
| 66 | 66 |
# Ignore bad email addresses and do not raise email delivery errors. |
| 67 | 67 |
# Set this to true and configure the email server for immediate delivery to raise delivery errors. |
@@ -11,7 +11,6 @@ Huginn::Application.routes.draw do |
||
| 11 | 11 |
post :propagate |
| 12 | 12 |
get :type_details |
| 13 | 13 |
get :event_descriptions |
| 14 |
- get :diagram |
|
| 15 | 14 |
end |
| 16 | 15 |
|
| 17 | 16 |
resources :logs, :only => [:index] do |
@@ -19,8 +18,12 @@ Huginn::Application.routes.draw do |
||
| 19 | 18 |
delete :clear |
| 20 | 19 |
end |
| 21 | 20 |
end |
| 21 |
+ |
|
| 22 |
+ resources :events, :only => [:index] |
|
| 22 | 23 |
end |
| 23 | 24 |
|
| 25 |
+ resource :diagram, :only => [:show] |
|
| 26 |
+ |
|
| 24 | 27 |
resources :events, :only => [:index, :show, :destroy] do |
| 25 | 28 |
member do |
| 26 | 29 |
post :reemit |
@@ -36,6 +39,8 @@ Huginn::Application.routes.draw do |
||
| 36 | 39 |
get :share |
| 37 | 40 |
get :export |
| 38 | 41 |
end |
| 42 |
+ |
|
| 43 |
+ resource :diagram, :only => [:show] |
|
| 39 | 44 |
end |
| 40 | 45 |
|
| 41 | 46 |
resources :user_credentials, :except => :show |
@@ -0,0 +1,21 @@ |
||
| 1 |
+class ConvertEfaSkipCreatedAt < ActiveRecord::Migration |
|
| 2 |
+ def up |
|
| 3 |
+ Agent.where(type: 'Agents::EventFormattingAgent').each do |agent| |
|
| 4 |
+ agent.options_will_change! |
|
| 5 |
+ unless agent.options.delete('skip_created_at').to_s == 'true'
|
|
| 6 |
+ agent.options['instructions'] = {
|
|
| 7 |
+ 'created_at' => '{{created_at}}'
|
|
| 8 |
+ }.update(agent.options['instructions'] || {})
|
|
| 9 |
+ end |
|
| 10 |
+ agent.save! |
|
| 11 |
+ end |
|
| 12 |
+ end |
|
| 13 |
+ |
|
| 14 |
+ def down |
|
| 15 |
+ Agent.where(type: 'Agents::EventFormattingAgent').each do |agent| |
|
| 16 |
+ agent.options_will_change! |
|
| 17 |
+ agent.options['skip_created_at'] = (agent.options['instructions'] || {})['created_at'] == '{{created_at}}'
|
|
| 18 |
+ agent.save! |
|
| 19 |
+ end |
|
| 20 |
+ end |
|
| 21 |
+end |
@@ -64,7 +64,7 @@ unless user.agents.where(:name => "Rain Notifier").exists? |
||
| 64 | 64 |
'value' => "rain|storm", |
| 65 | 65 |
'path' => "conditions" |
| 66 | 66 |
}], |
| 67 |
- 'message' => "Just so you know, it looks like '<conditions>' tomorrow in <location>" |
|
| 67 |
+ 'message' => "Just so you know, it looks like '{{conditions}}' tomorrow in {{location}}"
|
|
| 68 | 68 |
}).save! |
| 69 | 69 |
end |
| 70 | 70 |
|
@@ -15,12 +15,12 @@ describe EventsController do |
||
| 15 | 15 |
|
| 16 | 16 |
it "can filter by Agent" do |
| 17 | 17 |
sign_in users(:bob) |
| 18 |
- get :index, :agent => agents(:bob_website_agent) |
|
| 18 |
+ get :index, :agent_id => agents(:bob_website_agent) |
|
| 19 | 19 |
assigns(:events).length.should == agents(:bob_website_agent).events.length |
| 20 | 20 |
assigns(:events).all? {|i| i.agent.should == agents(:bob_website_agent) }.should be_true
|
| 21 | 21 |
|
| 22 | 22 |
lambda {
|
| 23 |
- get :index, :agent => agents(:jane_website_agent) |
|
| 23 |
+ get :index, :agent_id => agents(:jane_website_agent) |
|
| 24 | 24 |
}.should raise_error(ActiveRecord::RecordNotFound) |
| 25 | 25 |
end |
| 26 | 26 |
end |
@@ -0,0 +1,5 @@ |
||
| 1 |
+APP_SECRET_TOKEN=notarealappsecrettoken |
|
| 2 |
+TWITTER_OAUTH_KEY=twitteroauthkey |
|
| 3 |
+TWITTER_OAUTH_SECRET=twitteroauthsecret |
|
| 4 |
+THIRTY_SEVEN_SIGNALS_OAUTH_KEY=TESTKEY |
|
| 5 |
+THIRTY_SEVEN_SIGNALS_OAUTH_SECRET=TESTSECRET |
@@ -72,7 +72,7 @@ jane_rain_notifier_agent: |
||
| 72 | 72 |
:value => "rain", |
| 73 | 73 |
:path => "conditions" |
| 74 | 74 |
}], |
| 75 |
- :message => "Just so you know, it looks like '<conditions>' tomorrow in <location>" |
|
| 75 |
+ :message => "Just so you know, it looks like '{{conditions}}' tomorrow in {{location}}"
|
|
| 76 | 76 |
}.to_json.inspect %> |
| 77 | 77 |
|
| 78 | 78 |
bob_rain_notifier_agent: |
@@ -87,7 +87,7 @@ bob_rain_notifier_agent: |
||
| 87 | 87 |
:value => "rain", |
| 88 | 88 |
:path => "conditions" |
| 89 | 89 |
}], |
| 90 |
- :message => "Just so you know, it looks like '<conditions>' tomorrow in <location>" |
|
| 90 |
+ :message => "Just so you know, it looks like '{{conditions}}' tomorrow in {{location}}"
|
|
| 91 | 91 |
}.to_json.inspect %> |
| 92 | 92 |
|
| 93 | 93 |
bob_twitter_user_agent: |
@@ -56,13 +56,13 @@ describe DotHelper do |
||
| 56 | 56 |
it "generates a DOT script" do |
| 57 | 57 |
agents_dot(@agents).should =~ %r{
|
| 58 | 58 |
\A |
| 59 |
- digraph \s foo \{
|
|
| 59 |
+ digraph \x20 "Agent \x20 Event \x20 Flow" \{
|
|
| 60 | 60 |
node \[ [^\]]+ \]; |
| 61 | 61 |
(?<foo>\w+) \[label=foo\]; |
| 62 | 62 |
\k<foo> -> (?<bar1>\w+) \[style=dashed\]; |
| 63 | 63 |
\k<foo> -> (?<bar2>\w+) \[color="\#999999"\]; |
| 64 | 64 |
\k<bar1> \[label=bar1\]; |
| 65 |
- \k<bar2> \[label="bar2 \s \(Disabled\)",style="rounded,dashed",color="\#999999",fontcolor="\#999999"\]; |
|
| 65 |
+ \k<bar2> \[label=bar2,style="rounded,dashed",color="\#999999",fontcolor="\#999999"\]; |
|
| 66 | 66 |
\k<bar2> -> (?<bar3>\w+) \[style=dashed,color="\#999999"\]; |
| 67 | 67 |
\k<bar3> \[label=bar3\]; |
| 68 | 68 |
\} |
@@ -73,15 +73,15 @@ describe DotHelper do |
||
| 73 | 73 |
it "generates a richer DOT script" do |
| 74 | 74 |
agents_dot(@agents, true).should =~ %r{
|
| 75 | 75 |
\A |
| 76 |
- digraph \s foo \{
|
|
| 76 |
+ digraph \x20 "Agent \x20 Event \x20 Flow" \{
|
|
| 77 | 77 |
node \[ [^\]]+ \]; |
| 78 |
- (?<foo>\w+) \[label=foo,URL="#{Regexp.quote(agent_path(@foo))}"\];
|
|
| 78 |
+ (?<foo>\w+) \[label=foo,tooltip="Dot \x20 Foo",URL="#{Regexp.quote(agent_path(@foo))}"\];
|
|
| 79 | 79 |
\k<foo> -> (?<bar1>\w+) \[style=dashed\]; |
| 80 | 80 |
\k<foo> -> (?<bar2>\w+) \[color="\#999999"\]; |
| 81 |
- \k<bar1> \[label=bar1,URL="#{Regexp.quote(agent_path(@bar1))}"\];
|
|
| 82 |
- \k<bar2> \[label="bar2 \s \(Disabled\)",URL="#{Regexp.quote(agent_path(@bar2))}",style="rounded,dashed",color="\#999999",fontcolor="\#999999"\];
|
|
| 81 |
+ \k<bar1> \[label=bar1,tooltip="Dot \x20 Bar",URL="#{Regexp.quote(agent_path(@bar1))}"\];
|
|
| 82 |
+ \k<bar2> \[label=bar2,tooltip="Dot \x20 Bar",URL="#{Regexp.quote(agent_path(@bar2))}",style="rounded,dashed",color="\#999999",fontcolor="\#999999"\];
|
|
| 83 | 83 |
\k<bar2> -> (?<bar3>\w+) \[style=dashed,color="\#999999"\]; |
| 84 |
- \k<bar3> \[label=bar3,URL="#{Regexp.quote(agent_path(@bar3))}"\];
|
|
| 84 |
+ \k<bar3> \[label=bar3,tooltip="Dot \x20 Bar",URL="#{Regexp.quote(agent_path(@bar3))}"\];
|
|
| 85 | 85 |
\} |
| 86 | 86 |
\z |
| 87 | 87 |
}x |
@@ -9,6 +9,8 @@ describe Agents::EventFormattingAgent do |
||
| 9 | 9 |
:message => "Received {{content.text}} from {{content.name}} .",
|
| 10 | 10 |
:subject => "Weather looks like {{conditions}} according to the forecast at {{pretty_date.time}}",
|
| 11 | 11 |
:agent => "{{agent.type}}",
|
| 12 |
+ :created_at => "{{created_at}}",
|
|
| 13 |
+ :created_at_iso => "{{created_at | date:'%FT%T%:z'}}",
|
|
| 12 | 14 |
}, |
| 13 | 15 |
:mode => "clean", |
| 14 | 16 |
:matchers => [ |
@@ -18,7 +20,6 @@ describe Agents::EventFormattingAgent do |
||
| 18 | 20 |
:to => "pretty_date", |
| 19 | 21 |
}, |
| 20 | 22 |
], |
| 21 |
- :skip_created_at => "false" |
|
| 22 | 23 |
} |
| 23 | 24 |
} |
| 24 | 25 |
@checker = Agents::EventFormattingAgent.new(@valid_params) |
@@ -53,18 +54,12 @@ describe Agents::EventFormattingAgent do |
||
| 53 | 54 |
Event.last.payload[:content].should_not == nil |
| 54 | 55 |
end |
| 55 | 56 |
|
| 56 |
- it "should accept skip_created_at" do |
|
| 57 |
- @checker.receive([@event]) |
|
| 58 |
- Event.last.payload[:created_at].should_not == nil |
|
| 59 |
- @checker.options[:skip_created_at] = "true" |
|
| 60 |
- @checker.receive([@event]) |
|
| 61 |
- Event.last.payload[:created_at].should == nil |
|
| 62 |
- end |
|
| 63 |
- |
|
| 64 | 57 |
it "should handle Liquid templating in instructions" do |
| 65 | 58 |
@checker.receive([@event]) |
| 66 | 59 |
Event.last.payload[:message].should == "Received Some Lorem Ipsum from somevalue ." |
| 67 | 60 |
Event.last.payload[:agent].should == "WeatherAgent" |
| 61 |
+ Event.last.payload[:created_at].should == @event.created_at.to_s |
|
| 62 |
+ Event.last.payload[:created_at_iso].should == @event.created_at.iso8601 |
|
| 68 | 63 |
end |
| 69 | 64 |
|
| 70 | 65 |
it "should handle matchers and Liquid templating in instructions" do |
@@ -144,10 +139,5 @@ describe Agents::EventFormattingAgent do |
||
| 144 | 139 |
@checker.options[:mode] = "" |
| 145 | 140 |
@checker.should_not be_valid |
| 146 | 141 |
end |
| 147 |
- |
|
| 148 |
- it "should validate presence of skip_created_at" do |
|
| 149 |
- @checker.options[:skip_created_at] = "" |
|
| 150 |
- @checker.should_not be_valid |
|
| 151 |
- end |
|
| 152 | 142 |
end |
| 153 | 143 |
end |
@@ -7,19 +7,23 @@ describe Agents::FtpsiteAgent do |
||
| 7 | 7 |
@site = {
|
| 8 | 8 |
'expected_update_period_in_days' => 1, |
| 9 | 9 |
'url' => "ftp://ftp.example.org/pub/releases/", |
| 10 |
- 'patterns' => ["example-*.tar.gz"], |
|
| 10 |
+ 'patterns' => ["example*.tar.gz"], |
|
| 11 | 11 |
} |
| 12 | 12 |
@checker = Agents::FtpsiteAgent.new(:name => "Example", :options => @site, :keep_events_for => 2) |
| 13 | 13 |
@checker.user = users(:bob) |
| 14 | 14 |
@checker.save! |
| 15 |
- stub(@checker).each_entry.returns { |block|
|
|
| 16 |
- block.call("example-latest.tar.gz", Time.parse("2014-04-01T10:00:01Z"))
|
|
| 17 |
- block.call("example-1.0.tar.gz", Time.parse("2013-10-01T10:00:00Z"))
|
|
| 18 |
- block.call("example-1.1.tar.gz", Time.parse("2014-04-01T10:00:00Z"))
|
|
| 19 |
- } |
|
| 20 | 15 |
end |
| 21 | 16 |
|
| 22 | 17 |
describe "#check" do |
| 18 |
+ |
|
| 19 |
+ before do |
|
| 20 |
+ stub(@checker).each_entry.returns { |block|
|
|
| 21 |
+ block.call("example latest.tar.gz", Time.parse("2014-04-01T10:00:01Z"))
|
|
| 22 |
+ block.call("example-1.0.tar.gz", Time.parse("2013-10-01T10:00:00Z"))
|
|
| 23 |
+ block.call("example-1.1.tar.gz", Time.parse("2014-04-01T10:00:00Z"))
|
|
| 24 |
+ } |
|
| 25 |
+ end |
|
| 26 |
+ |
|
| 23 | 27 |
it "should validate the integer fields" do |
| 24 | 28 |
@checker.options['expected_update_period_in_days'] = "nonsense" |
| 25 | 29 |
lambda { @checker.save! }.should raise_error;
|
@@ -33,7 +37,7 @@ describe Agents::FtpsiteAgent do |
||
| 33 | 37 |
known_entries.sort_by(&:last).should == [ |
| 34 | 38 |
["example-1.0.tar.gz", "2013-10-01T10:00:00Z"], |
| 35 | 39 |
["example-1.1.tar.gz", "2014-04-01T10:00:00Z"], |
| 36 |
- ["example-latest.tar.gz", "2014-04-01T10:00:01Z"], |
|
| 40 |
+ ["example latest.tar.gz", "2014-04-01T10:00:01Z"], |
|
| 37 | 41 |
] |
| 38 | 42 |
} |
| 39 | 43 |
|
@@ -46,7 +50,7 @@ describe Agents::FtpsiteAgent do |
||
| 46 | 50 |
lambda { @checker.check }.should_not change { Event.count }
|
| 47 | 51 |
|
| 48 | 52 |
stub(@checker).each_entry.returns { |block|
|
| 49 |
- block.call("example-latest.tar.gz", Time.parse("2014-04-02T10:00:01Z"))
|
|
| 53 |
+ block.call("example latest.tar.gz", Time.parse("2014-04-02T10:00:01Z"))
|
|
| 50 | 54 |
|
| 51 | 55 |
# In the long list format the timestamp may look going |
| 52 | 56 |
# backwards after six months: Oct 01 10:00 -> Oct 01 2013 |
@@ -62,7 +66,7 @@ describe Agents::FtpsiteAgent do |
||
| 62 | 66 |
["example-1.0.tar.gz", "2013-10-01T00:00:00Z"], |
| 63 | 67 |
["example-1.1.tar.gz", "2014-04-01T10:00:00Z"], |
| 64 | 68 |
["example-1.2.tar.gz", "2014-04-02T10:00:00Z"], |
| 65 |
- ["example-latest.tar.gz", "2014-04-02T10:00:01Z"], |
|
| 69 |
+ ["example latest.tar.gz", "2014-04-02T10:00:01Z"], |
|
| 66 | 70 |
] |
| 67 | 71 |
} |
| 68 | 72 |
|
@@ -75,5 +79,33 @@ describe Agents::FtpsiteAgent do |
||
| 75 | 79 |
lambda { @checker.check }.should_not change { Event.count }
|
| 76 | 80 |
end |
| 77 | 81 |
end |
| 82 |
+ |
|
| 83 |
+ describe "#each_entry" do |
|
| 84 |
+ before do |
|
| 85 |
+ stub.any_instance_of(Net::FTP).list.returns [ # Windows format |
|
| 86 |
+ "04-02-14 10:01AM 288720748 example latest.tar.gz", |
|
| 87 |
+ "04-01-14 10:05AM 288720710 no-match-example.tar.gz" |
|
| 88 |
+ ] |
|
| 89 |
+ stub(@checker).open_ftp.yields Net::FTP.new |
|
| 90 |
+ end |
|
| 91 |
+ |
|
| 92 |
+ it "filters out files that don't match the given format" do |
|
| 93 |
+ entries = [] |
|
| 94 |
+ @checker.each_entry { |a, b| entries.push [a, b] }
|
|
| 95 |
+ |
|
| 96 |
+ entries.size.should == 1 |
|
| 97 |
+ filename, mtime = entries.first |
|
| 98 |
+ filename.should == 'example latest.tar.gz' |
|
| 99 |
+ mtime.should == '2014-04-02T10:01:00Z' |
|
| 100 |
+ end |
|
| 101 |
+ |
|
| 102 |
+ it "filters out files that are older than the given date" do |
|
| 103 |
+ @checker.options['after'] = '2015-10-21' |
|
| 104 |
+ entries = [] |
|
| 105 |
+ @checker.each_entry { |a, b| entries.push [a, b] }
|
|
| 106 |
+ entries.size.should == 0 |
|
| 107 |
+ end |
|
| 108 |
+ end |
|
| 109 |
+ |
|
| 78 | 110 |
end |
| 79 | 111 |
end |
@@ -42,6 +42,12 @@ describe Agents::HipchatAgent do |
||
| 42 | 42 |
@checker.should be_valid |
| 43 | 43 |
end |
| 44 | 44 |
|
| 45 |
+ it "should also allow a credential" do |
|
| 46 |
+ @checker.options['auth_token'] = nil |
|
| 47 |
+ @checker.should_not be_valid |
|
| 48 |
+ @checker.user.user_credentials.create :credential_name => 'hipchat_auth_token', :credential_value => 'something' |
|
| 49 |
+ @checker.reload.should be_valid |
|
| 50 |
+ end |
|
| 45 | 51 |
end |
| 46 | 52 |
|
| 47 | 53 |
describe "#receive" do |
@@ -85,6 +85,7 @@ describe EventDrop do |
||
| 85 | 85 |
before do |
| 86 | 86 |
@event = Event.new |
| 87 | 87 |
@event.agent = agents(:jane_weather_agent) |
| 88 |
+ @event.created_at = Time.at(1400000000) |
|
| 88 | 89 |
@event.payload = {
|
| 89 | 90 |
'title' => 'some title', |
| 90 | 91 |
'url' => 'http://some.site.example.org/', |
@@ -111,4 +112,9 @@ describe EventDrop do |
||
| 111 | 112 |
t = '{{agent.name}}'
|
| 112 | 113 |
interpolate(t, @event).should eq('SF Weather')
|
| 113 | 114 |
end |
| 115 |
+ |
|
| 116 |
+ it 'should have created_at' do |
|
| 117 |
+ t = '{{created_at | date:"%FT%T%z" }}'
|
|
| 118 |
+ interpolate(t, @event).should eq('2014-05-13T09:53:20-0700')
|
|
| 119 |
+ end |
|
| 114 | 120 |
end |
@@ -59,8 +59,6 @@ describe Service do |
||
| 59 | 59 |
stub_request(:post, "https://launchpad.37signals.com/authorization/token?client_id=TESTKEY&client_secret=TESTSECRET&refresh_token=refreshtokentest&type=refresh"). |
| 60 | 60 |
to_return(:status => 200, :body => '{"expires_in":1209600,"access_token": "NEWTOKEN"}', :headers => {})
|
| 61 | 61 |
@service.provider = '37signals' |
| 62 |
- ENV['THIRTY_SEVEN_SIGNALS_OAUTH_KEY'] = 'TESTKEY' |
|
| 63 |
- ENV['THIRTY_SEVEN_SIGNALS_OAUTH_SECRET'] = 'TESTSECRET' |
|
| 64 | 62 |
@service.refresh_token = 'refreshtokentest' |
| 65 | 63 |
@service.refresh_token! |
| 66 | 64 |
@service.token.should == 'NEWTOKEN' |
@@ -1,4 +1,3 @@ |
||
| 1 |
-# This file is copied to spec/ when you run 'rails generate rspec:install' |
|
| 2 | 1 |
ENV["RAILS_ENV"] ||= 'test' |
| 3 | 2 |
|
| 4 | 3 |
if ENV['COVERAGE'] |
@@ -9,6 +8,10 @@ else |
||
| 9 | 8 |
Coveralls.wear!('rails')
|
| 10 | 9 |
end |
| 11 | 10 |
|
| 11 |
+# Required ENV variables that are normally set in .env are setup here for the test environment. |
|
| 12 |
+require 'dotenv' |
|
| 13 |
+Dotenv.load File.join(File.dirname(__FILE__), "env.test") |
|
| 14 |
+ |
|
| 12 | 15 |
require File.expand_path("../../config/environment", __FILE__)
|
| 13 | 16 |
require 'rspec/rails' |
| 14 | 17 |
require 'rspec/autorun' |
@@ -19,7 +22,7 @@ WebMock.disable_net_connect! |
||
| 19 | 22 |
|
| 20 | 23 |
# Requires supporting ruby files with custom matchers and macros, etc, |
| 21 | 24 |
# in spec/support/ and its subdirectories. |
| 22 |
-Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
|
|
| 25 |
+Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
|
|
| 23 | 26 |
|
| 24 | 27 |
ActiveRecord::Migration.maintain_test_schema! |
| 25 | 28 |
|